#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>

 /*
    leds{
        compatible = "minli,leds";
        led1-gpio = <&gpioe 10 0>;
        led2-gpio = <&gpiof 10 0>;
        led3-gpio = <&gpioe 8 0>;
    };
 */

MODULE_LICENSE("GPL");

#define LED_ON		_IOW('l', 1, int)
#define LED_OFF		_IOW('l', 0, int)
#define LED_STAT	_IOWR('l', 2, int)


static dev_t ledDevNum;//设备号
static struct cdev* ledCDev;//字符设备结构体
static struct class* ledClass;//自动创建设备文件需要的结构体
static int ledPins[3] = {0};

long led_ioctl(struct file * fp, unsigned int request, unsigned long arg)
{
    int ledIndex = 0, ledStatus;
    copy_from_user(&ledIndex,(short*)arg,_IOC_SIZE(request));
    switch(request)
    {
        case LED_ON:
        {
            gpio_set_value(ledPins[ledIndex-1],1);
            break;
        }
        case LED_OFF:
        {
            gpio_set_value(ledPins[ledIndex-1],0);
            break;
        }
        case LED_STAT:
        {
            ledStatus = gpio_get_value(ledPins[ledIndex-1]);
            copy_to_user((void*)arg,&ledStatus,_IOC_SIZE(request));
            break;
        }
        default:return -EINVAL;
    }
    return 0;
}

static struct file_operations led_fops = 
{
    .unlocked_ioctl = led_ioctl
};



int led_drv_probe(struct platform_device* ledDev)
{
    struct device_node* ledDTNode;
    int i=0;
    char ledLabelBuf[30];

    alloc_chrdev_region(&ledDevNum,0,1,"leds");
    ledCDev = cdev_alloc();
    cdev_init(ledCDev, &led_fops);
    cdev_add(ledCDev, ledDevNum, 1);

    ledClass = class_create(THIS_MODULE,"LEDS");
    device_create(ledClass,NULL,ledDevNum,NULL,"leds");

    ledDTNode = of_find_node_by_path("/leds");
    if(ledDTNode == NULL)
    {
        printk("not find node\n");
        return -ENODEV;
    }

    ledPins[0] = of_get_named_gpio(ledDTNode,"led1-gpio",0);
    ledPins[1] = of_get_named_gpio(ledDTNode,"led2-gpio",0);
    ledPins[2] = of_get_named_gpio(ledDTNode,"led3-gpio",0);
    
    for(i=0;i<3;i++)
    {
        sprintf(ledLabelBuf,"led%d",i+1);
        gpio_request(ledPins[i],ledLabelBuf);

        gpio_direction_output(ledPins[i],0);
    }
    return 0;
}

int led_drv_remove(struct platform_device* ledDev)
{
    int i;

    for(i=0;i<3;i++)
    {
        gpio_free(ledPins[i]);
    }

    device_destroy(ledClass,ledDevNum);
    class_destroy(ledClass);

    cdev_del(ledCDev);
    unregister_chrdev_region(ledDevNum,1);
    return 0;
}

struct of_device_id ledMatchTable[] = 
{
    {.compatible = "minli,leds"},
    {}
};

struct platform_driver ledDrv = 
{
    .probe = led_drv_probe,
    .remove = led_drv_remove,
    .driver = 
    {
        .name = "xxxxxx",
        .of_match_table = ledMatchTable
    }
};

static int __init led_drv_init(void)
{
    platform_driver_register(&ledDrv);
    return 0;
}

static void __exit led_drv_exit(void)
{
    platform_driver_unregister(&ledDrv);
}

module_init(led_drv_init);
module_exit(led_drv_exit);